<?php

namespace wchat\wx\V3;

use Exception;
use OpenSSLAsymmetricKey;
use Psr\Http\Message\RequestInterface;
use wchat\wx\SmallProgram;
use wchat\wx\V3\Notify\GoodsDetail;
use wchat\wx\V3\Notify\NotifyModel;
use wchat\wx\V3\Notify\PromotionDetail;

const KEY_TYPE_PUBLIC  = 'public';
const KEY_TYPE_PRIVATE = 'private';

class WxV3PaymentNotify extends SmallProgram
{


    use WxV3PaymentTait;


    /**
     * @param string $id
     * @param string $create_time
     * @param string $resource_type
     * @param string $event_type
     * @param string $summary
     * @param array $resource
     */
    public function __construct(
        public string $id = "EV-2018022511223320873",
        public string $create_time = "2015-05-20T13:29:35+08:00",
        public string $resource_type = "encrypt-resource",
        public string $event_type = "TRANSACTION.SUCCESS",
        public string $summary = "支付成功",
        public array  $resource = []
    )
    {
    }


    /**
     * @var NotifyModel
     */
    public NotifyModel $notifyModel;


    /**
     * @param RequestInterface $request
     * @return bool
     * @throws
     */
    public function verify(RequestInterface $request): bool
    {
        $platformPublicKeyInstance = $this->rsaFrom($this->payConfig->pay->wx->mchKey, KEY_TYPE_PUBLIC);
        $inWechatpaySignature      = $request->getHeaderLine('wechatpay-signature');    // 请根据实际情况获取
        $inWechatpayTimestamp      = $request->getHeaderLine('wechatpay-timestamp');    // 请根据实际情况获取
        $inWechatpayNonce          = $request->getHeaderLine('wechatpay-nonce');        // 请根据实际情况获取
        $inBody                    = $request->getBody()->getContents();                // 请根据实际情况获取，例如: file_get_contents('php://input');
        $timeOffsetStatus          = 300 >= abs(time() - (int)$inWechatpayTimestamp);
        $verifiedStatus            = $this->notifyVerify(
            $this->lineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $platformPublicKeyInstance);

        $this->decode($this->resource['ciphertext'], $this->resource['nonce'], $this->resource['associated_data']);

        if (!$timeOffsetStatus || !$verifiedStatus) {
            return false;
        } else {
            return true;
        }
    }


    /**
     * @param ...$pieces
     * @return string
     */
    protected function lineFeed(...$pieces): string
    {
        return implode("\n", array_merge($pieces, ['']));
    }


    /**
     * @return string|bool
     */
    protected function body(): string|bool
    {
        return json_encode(['id' => $this->id, 'create_time' => $this->create_time, 'resource_type' => $this->resource_type, 'event_type' => $this->event_type, 'summary' => $this->summary, 'resource' => $this->resource]);
    }


    /**
     * @param string $message
     * @param string $signature
     * @param OpenSSLAsymmetricKey $publicKey
     * @return bool
     */
    protected function notifyVerify(string $message, string $signature, OpenSSLAsymmetricKey $publicKey): bool
    {
        if (($result = openssl_verify($message, base64_decode($signature), $publicKey, OPENSSL_ALGO_SHA256)) === false) {
            throw new \UnexpectedValueException('Verified the input $message failed, please checking your $publicKey whether or nor correct.');
        }
        return $result === 1;
    }


    /**
     * @param string $thing
     * @param string $type
     * @return OpenSSLAsymmetricKey
     */
    protected function rsaFrom(string $thing, string $type = KEY_TYPE_PRIVATE): OpenSSLAsymmetricKey
    {
        $pkey = (($isPublic = $type === KEY_TYPE_PUBLIC) ? openssl_pkey_get_public('file://' . $thing) : openssl_pkey_get_private('file://' . $thing));
        if (false === $pkey) {
            throw new \UnexpectedValueException(sprintf('Cannot load %s from(%s), please take care about the $thing input.', $isPublic ? 'publicKey' : 'privateKey', gettype($thing)));
        }
        return $pkey;
    }

    /**
     * @param string $ciphertext
     * @param string $nonce
     * @param string $associated_data
     * @return bool
     */
    public function decode(string $ciphertext, string $nonce, string $associated_data): bool
    {
        $data                                = $this->decrypt($ciphertext, $this->payConfig->pay->wx->secret, $nonce, $associated_data);
        $this->notifyModel                   = new NotifyModel();
        $this->notifyModel->amount           = $data['amount'];
        $this->notifyModel->payer            = $data['payer'];
        $this->notifyModel->scene_info       = $data['payer'];
        $this->notifyModel->appid            = $data['appid'];
        $this->notifyModel->mchid            = $data['mchid'];
        $this->notifyModel->out_trade_no     = $data['out_trade_no'];
        $this->notifyModel->transaction_id   = $data['transaction_id'];
        $this->notifyModel->trade_type       = $data['trade_type'];
        $this->notifyModel->trade_state      = $data['trade_state'];
        $this->notifyModel->trade_state_desc = $data['trade_state_desc'];
        $this->notifyModel->bank_type        = $data['bank_type'];
        $this->notifyModel->attach           = $data['attach'];
        $this->notifyModel->success_time     = $data['success_time'];
        $this->notifyModel->promotion_detail = [];
        if (!isset($data['promotion_detail'])) {
            return true;
        }
        foreach ($data['promotion_detail'] as $datum) {
            $detail                       = new PromotionDetail();
            $detail->amount               = $datum['amount'];
            $detail->wechatpay_contribute = $datum['wechatpay_contribute'];
            $detail->coupon_id            = $datum['coupon_id'];
            $detail->scope                = $datum['scope'];
            $detail->merchant_contribute  = $datum['merchant_contribute'];
            $detail->name                 = $datum['name'];
            $detail->other_contribute     = $datum['other_contribute'];
            $detail->currency             = $datum['currency'];
            $detail->stock_id             = $datum['stock_id'];
            $detail->goods_detail         = [];
            if (isset($data['goods_detail'])) {
                foreach ($datum['goods_detail'] as $value) {
                    $detail->goods_detail[] = new GoodsDetail($value);
                }
            }
            $this->notifyModel->promotion_detail[] = $detail;
        }
        return true;
    }


}
